// Copyright Epic Games, Inc. All Rights Reserved.

#include "zencore/compactbinary.h"

#include <zencore/assertfmt.h>
#include <zencore/base64.h>
#include <zencore/compactbinarybuilder.h>
#include <zencore/compactbinaryvalidation.h>
#include <zencore/compactbinaryvalue.h>
#include <zencore/compress.h>
#include <zencore/endian.h>
#include <zencore/fmtutils.h>
#include <zencore/stream.h>
#include <zencore/string.h>
#include <zencore/testing.h>
#include <zencore/uid.h>

#include <fmt/format.h>
#include <string_view>

#if ZEN_PLATFORM_WINDOWS
#	include <zencore/windows.h>
#else
#	include <time.h>
#endif

namespace zen {

const int DaysToMonth[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};

double
GetJulianDay(uint64_t Ticks)
{
	return (double)(1721425.5 + Ticks / TimeSpan::TicksPerDay);
}

bool
IsLeapYear(int Year)
{
	if ((Year % 4) == 0)
	{
		return (((Year % 100) != 0) || ((Year % 400) == 0));
	}

	return false;
}

static constexpr uint64_t
GetPlatformToDateTimeBiasInSeconds()
{
#if ZEN_PLATFORM_WINDOWS
	const uint64_t PlatformEpochYear = 1601;
#else
	const uint64_t PlatformEpochYear	 = 1970;
#endif
	const uint64_t DateTimeEpochYear = 1;
	return uint64_t(double(PlatformEpochYear - DateTimeEpochYear) * 365.2425) * 86400;
}

uint64_t
DateTime::NowTicks()
{
	static constexpr uint64_t EpochBias = GetPlatformToDateTimeBiasInSeconds();

#if ZEN_PLATFORM_WINDOWS
	FILETIME SysTime;
	GetSystemTimePreciseAsFileTime(&SysTime);
	return (EpochBias * TimeSpan::TicksPerSecond) + ((uint64_t(SysTime.dwHighDateTime) << 32) | SysTime.dwLowDateTime);
#else
	int64_t		   SecondsSinceUnixEpoch = time(nullptr);
	return (EpochBias + SecondsSinceUnixEpoch) * TimeSpan::TicksPerSecond;
#endif
}

DateTime
DateTime::Now()
{
	return DateTime{NowTicks()};
}

void
DateTime::Set(int Year, int Month, int Day, int Hour, int Minute, int Second, int MilliSecond)
{
	int TotalDays = 0;

	if ((Month > 2) && IsLeapYear(Year))
	{
		++TotalDays;
	}

	--Year;	  // the current year is not a full year yet
	--Month;  // the current month is not a full month yet

	TotalDays += Year * 365;
	TotalDays += Year / 4;			  // leap year day every four years...
	TotalDays -= Year / 100;		  // ...except every 100 years...
	TotalDays += Year / 400;		  // ...but also every 400 years
	TotalDays += DaysToMonth[Month];  // days in this year up to last month
	TotalDays += Day - 1;			  // days in this month minus today

	Ticks = TotalDays * TimeSpan::TicksPerDay + Hour * TimeSpan::TicksPerHour + Minute * TimeSpan::TicksPerMinute +
			Second * TimeSpan::TicksPerSecond + MilliSecond * TimeSpan::TicksPerMillisecond;
}

int
DateTime::GetYear() const
{
	int Year, Month, Day;
	GetDate(Year, Month, Day);

	return Year;
}

int
DateTime::GetMonth() const
{
	int Year, Month, Day;
	GetDate(Year, Month, Day);

	return Month;
}

int
DateTime::GetDay() const
{
	int Year, Month, Day;
	GetDate(Year, Month, Day);

	return Day;
}

int
DateTime::GetHour() const
{
	return (int)((Ticks / TimeSpan::TicksPerHour) % 24);
}

int
DateTime::GetHour12() const
{
	int Hour = GetHour();

	if (Hour < 1)
	{
		return 12;
	}

	if (Hour > 12)
	{
		return (Hour - 12);
	}

	return Hour;
}

int
DateTime::GetMinute() const
{
	return (int)((Ticks / TimeSpan::TicksPerMinute) % 60);
}

int
DateTime::GetSecond() const
{
	return (int)((Ticks / TimeSpan::TicksPerSecond) % 60);
}

int
DateTime::GetMillisecond() const
{
	return (int)((Ticks / TimeSpan::TicksPerMillisecond) % 1000);
}

void
DateTime::GetDate(int& Year, int& Month, int& Day) const
{
	// Based on FORTRAN code in:
	// Fliegel, H. F. and van Flandern, T. C.,
	// Communications of the ACM, Vol. 11, No. 10 (October 1968).

	int i, j, k, l, n;

	l = int(GetJulianDay(Ticks) + 0.5) + 68569;
	n = 4 * l / 146097;
	l = l - (146097 * n + 3) / 4;
	i = 4000 * (l + 1) / 1461001;
	l = l - 1461 * i / 4 + 31;
	j = 80 * l / 2447;
	k = l - 2447 * j / 80;
	l = j / 11;
	j = j + 2 - 12 * l;
	i = 100 * (n - 49) + i + l;

	Year  = i;
	Month = j;
	Day	  = k;
}

std::string
DateTime::ToString(const char* Format) const
{
	ExtendableStringBuilder<32> Result;
	int							Year, Month, Day;

	GetDate(Year, Month, Day);

	if (Format != nullptr)
	{
		while (*Format != '\0')
		{
			if ((*Format == '%') && (*(++Format) != '\0'))
			{
				switch (*Format)
				{
					// case 'a': Result.Append(IsMorning() ? TEXT("am") : TEXT("pm")); break;
					// case 'A': Result.Append(IsMorning() ? TEXT("AM") : TEXT("PM")); break;
					case 'd':
						Result.Append(fmt::format("{:02}", Day));
						break;
						// case 'D': Result.Appendf(TEXT("%03i"), GetDayOfYear()); break;
					case 'm':
						Result.Append(fmt::format("{:02}", Month));
						break;
					case 'y':
						Result.Append(fmt::format("{:02}", Year % 100));
						break;
					case 'Y':
						Result.Append(fmt::format("{:04}", Year));
						break;
					case 'h':
						Result.Append(fmt::format("{:02}", GetHour12()));
						break;
					case 'H':
						Result.Append(fmt::format("{:02}", GetHour()));
						break;
					case 'M':
						Result.Append(fmt::format("{:02}", GetMinute()));
						break;
					case 'S':
						Result.Append(fmt::format("{:02}", GetSecond()));
						break;
					case 's':
						Result.Append(fmt::format("{:03}", GetMillisecond()));
						break;
					default:
						Result.Append(*Format);
				}
			}
			else
			{
				Result.Append(*Format);
			}

			// move to the next one
			Format++;
		}
	}

	return Result.ToString();
}

std::string
DateTime::ToIso8601() const
{
	return ToString("%Y-%m-%dT%H:%M:%S.%sZ");
}

void
TimeSpan::Set(int Days, int Hours, int Minutes, int Seconds, int FractionNano)
{
	int64_t TotalTicks = 0;

	TotalTicks += Days * TicksPerDay;
	TotalTicks += Hours * TicksPerHour;
	TotalTicks += Minutes * TicksPerMinute;
	TotalTicks += Seconds * TicksPerSecond;
	TotalTicks += FractionNano / NanosecondsPerTick;

	Ticks = TotalTicks;
}

std::string
TimeSpan::ToString(const char* Format) const
{
	StringBuilder<128> Result;

	Result.Append((int64_t(Ticks) < 0) ? '-' : '+');

	while (*Format != '\0')
	{
		if ((*Format == '%') && (*++Format != '\0'))
		{
			switch (*Format)
			{
				case 'd':
					Result.Append(fmt::format("{}", GetDays()));
					break;
				case 'D':
					Result.Append(fmt::format("{:08}", GetDays()));
					break;
				case 'h':
					Result.Append(fmt::format("{:02}", GetHours()));
					break;
				case 'm':
					Result.Append(fmt::format("{:02}", GetMinutes()));
					break;
				case 's':
					Result.Append(fmt::format("{:02}", GetSeconds()));
					break;
				case 'f':
					Result.Append(fmt::format("{:03}", GetFractionMilli()));
					break;
				case 'u':
					Result.Append(fmt::format("{:06}", GetFractionMicro()));
					break;
				case 't':
					Result.Append(fmt::format("{:07}", GetFractionTicks()));
					break;
				case 'n':
					Result.Append(fmt::format("{:09}", GetFractionNano()));
					break;
				default:
					Result.Append(*Format);
			}
		}
		else
		{
			Result.Append(*Format);
		}

		++Format;
	}

	return Result.ToString();
}

std::string
TimeSpan::ToString() const
{
	if (GetDays() == 0)
	{
		return ToString("%h:%m:%s.%f");
	}

	return ToString("%d.%h:%m:%s.%f");
}

StringBuilderBase&
Guid::ToString(StringBuilderBase& Sb) const
{
	char Buf[128];
	snprintf(Buf, sizeof Buf, "%08x-%04x-%04x-%04x-%04x%08x", A, B >> 16, B & 0xFFFF, C >> 16, C & 0xFFFF, D);
	Sb << Buf;

	return Sb;
}

Guid
Guid::FromString(std::string_view InString)
{
	if (InString.size() != 36)
	{
	}

	bool Ok = true;

	uint32_t V0 = 0, V5 = 0;
	uint16_t V1 = 0, V2 = 0, V3 = 0, V4 = 0;

	Ok = Ok && ParseHexNumber(InString.substr(0, 8), /* out */ V0);
	Ok = Ok && ParseHexNumber(InString.substr(9, 4), /* out */ V1);
	Ok = Ok && ParseHexNumber(InString.substr(14, 4), /* out */ V2);
	Ok = Ok && ParseHexNumber(InString.substr(19, 4), /* out */ V3);
	Ok = Ok && ParseHexNumber(InString.substr(24, 4), /* out */ V4);
	Ok = Ok && ParseHexNumber(InString.substr(28, 8), /* out */ V5);

	Guid Value;
	Value.A = V0;
	Value.B = V1 << 16 | V2;
	Value.C = V3 << 16 | V4;
	Value.D = V5;

	return Value;
}

//////////////////////////////////////////////////////////////////////////

namespace CompactBinaryPrivate {
	static constexpr const uint8_t GEmptyObjectPayload[] = {uint8_t(CbFieldType::Object), 0x00};
	static constexpr const uint8_t GEmptyArrayPayload[]	 = {uint8_t(CbFieldType::Array), 0x01, 0x00};
}  // namespace CompactBinaryPrivate

//////////////////////////////////////////////////////////////////////////

CbFieldView::CbFieldView(const void* DataPointer, CbFieldType FieldType)
{
	const uint8_t*	  Bytes		= static_cast<const uint8_t*>(DataPointer);
	const CbFieldType LocalType = CbFieldTypeOps::HasFieldType(FieldType) ? (CbFieldType(*Bytes++) | CbFieldType::HasFieldType) : FieldType;

	uint32_t	   NameLenByteCount = 0;
	const uint64_t NameLen64		= CbFieldTypeOps::HasFieldName(LocalType) ? ReadVarUInt(Bytes, NameLenByteCount) : 0;
	Bytes += NameLen64 + NameLenByteCount;

	Type	= LocalType;
	NameLen = uint32_t(std::clamp<uint64_t>(NameLen64, 0, ~uint32_t(0)));
	Payload = Bytes;
}

void
CbFieldView::IterateAttachments(std::function<void(CbFieldView)> Visitor) const
{
	switch (CbFieldTypeOps::GetType(Type))
	{
		case CbFieldType::Object:
		case CbFieldType::UniformObject:
			return CbObjectView::FromFieldView(*this).IterateAttachments(Visitor);
		case CbFieldType::Array:
		case CbFieldType::UniformArray:
			return CbArrayView::FromFieldView(*this).IterateAttachments(Visitor);
		case CbFieldType::ObjectAttachment:
		case CbFieldType::BinaryAttachment:
			return Visitor(*this);
		default:
			return;
	}
}

CbObjectView
CbFieldView::AsObjectView()
{
	if (CbFieldTypeOps::IsObject(Type))
	{
		Error = CbFieldError::None;
		return CbObjectView::FromFieldView(*this);
	}
	else
	{
		Error = CbFieldError::TypeError;
		return CbObjectView();
	}
}

CbArrayView
CbFieldView::AsArrayView()
{
	if (CbFieldTypeOps::IsArray(Type))
	{
		Error = CbFieldError::None;
		return CbArrayView::FromFieldView(*this);
	}
	else
	{
		Error = CbFieldError::TypeError;
		return CbArrayView();
	}
}

MemoryView
CbFieldView::AsBinaryView(const MemoryView Default)
{
	if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsBinary(Accessor.GetType()))
	{
		Error = CbFieldError::None;
		return Accessor.AsBinary();
	}
	else
	{
		Error = CbFieldError::TypeError;
		return Default;
	}
}

std::string_view
CbFieldView::AsString(const std::string_view Default)
{
	if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsString(Accessor.GetType()))
	{
		Error = CbFieldError::None;
		return Accessor.AsString();
	}
	else
	{
		Error = CbFieldError::TypeError;
		return Default;
	}
}

std::u8string_view
CbFieldView::AsU8String(const std::u8string_view Default)
{
	if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsString(Accessor.GetType()))
	{
		Error = CbFieldError::None;
		return Accessor.AsU8String();
	}
	else
	{
		Error = CbFieldError::TypeError;
		return Default;
	}
}

uint64_t
CbFieldView::AsInteger(const uint64_t Default, const CompactBinaryPrivate::IntegerParams Params)
{
	if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsInteger(Accessor.GetType()))
	{
		return Accessor.AsInteger(Params, &Error, Default);
	}
	else
	{
		Error = CbFieldError::TypeError;
		return Default;
	}
}

float
CbFieldView::AsFloat(const float Default)
{
	switch (CbValue Accessor = GetValue(); Accessor.GetType())
	{
		case CbFieldType::IntegerPositive:
		case CbFieldType::IntegerNegative:
			{
				const uint64_t	   IsNegative	  = uint8_t(Accessor.GetType()) & 1;
				constexpr uint64_t OutOfRangeMask = ~((uint64_t(1) << /*FLT_MANT_DIG*/ 24) - 1);

				uint32_t	   MagnitudeByteCount;
				const int64_t  Magnitude = ReadVarUInt(Accessor.GetData(), MagnitudeByteCount) + IsNegative;
				const uint64_t IsInRange = !(Magnitude & OutOfRangeMask);
				Error					 = IsInRange ? CbFieldError::None : CbFieldError::RangeError;
				return IsInRange ? float(IsNegative ? -Magnitude : Magnitude) : Default;
			}
		case CbFieldType::Float32:
			{
				Error = CbFieldError::None;
				return Accessor.AsFloat32();
			}
		case CbFieldType::Float64:
			Error = CbFieldError::RangeError;
			return Default;
		default:
			Error = CbFieldError::TypeError;
			return Default;
	}
}

double
CbFieldView::AsDouble(const double Default)
{
	switch (CbValue Accessor = GetValue(); Accessor.GetType())
	{
		case CbFieldType::IntegerPositive:
		case CbFieldType::IntegerNegative:
			{
				const uint64_t	   IsNegative	  = uint8_t(Accessor.GetType()) & 1;
				constexpr uint64_t OutOfRangeMask = ~((uint64_t(1) << /*DBL_MANT_DIG*/ 53) - 1);

				uint32_t	   MagnitudeByteCount;
				const int64_t  Magnitude = ReadVarUInt(Accessor.GetData(), MagnitudeByteCount) + IsNegative;
				const uint64_t IsInRange = !(Magnitude & OutOfRangeMask);
				Error					 = IsInRange ? CbFieldError::None : CbFieldError::RangeError;
				return IsInRange ? double(IsNegative ? -Magnitude : Magnitude) : Default;
			}
		case CbFieldType::Float32:
			{
				Error = CbFieldError::None;
				return Accessor.AsFloat32();
			}
		case CbFieldType::Float64:
			{
				Error = CbFieldError::None;
				return Accessor.AsFloat64();
			}
		default:
			Error = CbFieldError::TypeError;
			return Default;
	}
}

bool
CbFieldView::AsBool(const bool bDefault)
{
	CbValue	   Accessor = GetValue();
	const bool IsBool	= CbFieldTypeOps::IsBool(Accessor.GetType());
	Error				= IsBool ? CbFieldError::None : CbFieldError::TypeError;
	return (uint8_t(IsBool) & Accessor.AsBool()) | ((!IsBool) & bDefault);
}

IoHash
CbFieldView::AsObjectAttachment(const IoHash& Default)
{
	if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsObjectAttachment(Accessor.GetType()))
	{
		Error = CbFieldError::None;
		return Accessor.AsObjectAttachment();
	}
	else
	{
		Error = CbFieldError::TypeError;
		return Default;
	}
}

IoHash
CbFieldView::AsBinaryAttachment(const IoHash& Default)
{
	if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsBinaryAttachment(Accessor.GetType()))
	{
		Error = CbFieldError::None;
		return Accessor.AsBinaryAttachment();
	}
	else
	{
		Error = CbFieldError::TypeError;
		return Default;
	}
}

IoHash
CbFieldView::AsAttachment(const IoHash& Default)
{
	if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsAttachment(Accessor.GetType()))
	{
		Error = CbFieldError::None;
		return Accessor.AsAttachment();
	}
	else
	{
		Error = CbFieldError::TypeError;
		return Default;
	}
}

IoHash
CbFieldView::AsHash(const IoHash& Default)
{
	if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsHash(Accessor.GetType()))
	{
		Error = CbFieldError::None;
		return Accessor.AsHash();
	}
	else
	{
		Error = CbFieldError::TypeError;
		return Default;
	}
}

Guid
CbFieldView::AsUuid()
{
	return AsUuid(Guid());
}

Guid
CbFieldView::AsUuid(const Guid& Default)
{
	if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsUuid(Accessor.GetType()))
	{
		Error = CbFieldError::None;
		return Accessor.AsUuid();
	}
	else
	{
		Error = CbFieldError::TypeError;
		return Default;
	}
}

Oid
CbFieldView::AsObjectId()
{
	return AsObjectId(Oid());
}

Oid
CbFieldView::AsObjectId(const Oid& Default)
{
	if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsObjectId(Accessor.GetType()))
	{
		Error = CbFieldError::None;
		return Accessor.AsObjectId();
	}
	else
	{
		Error = CbFieldError::TypeError;
		return Default;
	}
}

CbCustomById
CbFieldView::AsCustomById(CbCustomById Default)
{
	if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsCustomById(Accessor.GetType()))
	{
		Error = CbFieldError::None;
		return Accessor.AsCustomById();
	}
	else
	{
		Error = CbFieldError::TypeError;
		return Default;
	}
}

CbCustomByName
CbFieldView::AsCustomByName(CbCustomByName Default)
{
	if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsCustomByName(Accessor.GetType()))
	{
		Error = CbFieldError::None;
		return Accessor.AsCustomByName();
	}
	else
	{
		Error = CbFieldError::TypeError;
		return Default;
	}
}

int64_t
CbFieldView::AsDateTimeTicks(const int64_t Default)
{
	if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsDateTime(Accessor.GetType()))
	{
		Error = CbFieldError::None;
		return Accessor.AsDateTimeTicks();
	}
	else
	{
		Error = CbFieldError::TypeError;
		return Default;
	}
}

DateTime
CbFieldView::AsDateTime()
{
	return DateTime(AsDateTimeTicks(0));
}

DateTime
CbFieldView::AsDateTime(DateTime Default)
{
	return DateTime(AsDateTimeTicks(Default.GetTicks()));
}

int64_t
CbFieldView::AsTimeSpanTicks(const int64_t Default)
{
	if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsTimeSpan(Accessor.GetType()))
	{
		Error = CbFieldError::None;
		return Accessor.AsTimeSpanTicks();
	}
	else
	{
		Error = CbFieldError::TypeError;
		return Default;
	}
}

TimeSpan
CbFieldView::AsTimeSpan()
{
	return TimeSpan(AsTimeSpanTicks(0));
}

TimeSpan
CbFieldView::AsTimeSpan(TimeSpan Default)
{
	return TimeSpan(AsTimeSpanTicks(Default.GetTicks()));
}

uint64_t
CbFieldView::GetSize() const
{
	return sizeof(CbFieldType) + GetViewNoType().GetSize();
}

uint64_t
CbFieldView::GetPayloadSize() const
{
	switch (CbFieldTypeOps::GetType(Type))
	{
		case CbFieldType::None:
		case CbFieldType::Null:
			return 0;
		case CbFieldType::Object:
		case CbFieldType::UniformObject:
		case CbFieldType::Array:
		case CbFieldType::UniformArray:
		case CbFieldType::Binary:
		case CbFieldType::String:
			{
				uint32_t	   PayloadSizeByteCount;
				const uint64_t PayloadSize = ReadVarUInt(Payload, PayloadSizeByteCount);
				return PayloadSize + PayloadSizeByteCount;
			}
		case CbFieldType::IntegerPositive:
		case CbFieldType::IntegerNegative:
			return MeasureVarUInt(Payload);
		case CbFieldType::Float32:
			return 4;
		case CbFieldType::Float64:
			return 8;
		case CbFieldType::BoolFalse:
		case CbFieldType::BoolTrue:
			return 0;
		case CbFieldType::ObjectAttachment:
		case CbFieldType::BinaryAttachment:
		case CbFieldType::Hash:
			return 20;
		case CbFieldType::Uuid:
			return 16;
		case CbFieldType::ObjectId:
			return 12;
		case CbFieldType::DateTime:
		case CbFieldType::TimeSpan:
			return 8;
		default:
			return 0;
	}
}

IoHash
CbFieldView::GetHash() const
{
	IoHashStream HashStream;
	GetHash(HashStream);
	return HashStream.GetHash();
}

void
CbFieldView::GetHash(IoHashStream& Hash) const
{
	const CbFieldType SerializedType = CbFieldTypeOps::GetSerializedType(Type);
	Hash.Append(&SerializedType, sizeof(SerializedType));
	auto View = GetViewNoType();
	Hash.Append(View.GetData(), View.GetSize());
}

bool
CbFieldView::Equals(const CbFieldView& Other) const
{
	return CbFieldTypeOps::GetSerializedType(Type) == CbFieldTypeOps::GetSerializedType(Other.Type) &&
		   GetViewNoType().EqualBytes(Other.GetViewNoType());
}

void
CbFieldView::CopyTo(MutableMemoryView Buffer) const
{
	const MemoryView Source = GetViewNoType();
	ZEN_ASSERT_FORMAT(Buffer.GetSize() == sizeof(CbFieldType) + Source.GetSize(),
					  "A buffer of {} bytes was provided when {} bytes are required",
					  Buffer.GetSize(),
					  sizeof(CbFieldType) + Source.GetSize());
	*static_cast<CbFieldType*>(Buffer.GetData()) = CbFieldTypeOps::GetSerializedType(Type);
	Buffer.RightChopInline(sizeof(CbFieldType));
	memcpy(Buffer.GetData(), Source.GetData(), Source.GetSize());
}

void
CbFieldView::CopyTo(BinaryWriter& Ar) const
{
	const MemoryView SourceView		= GetViewNoType();
	CbFieldType		 SerializedType = CbFieldTypeOps::GetSerializedType(Type);
	const MemoryView TypeView(reinterpret_cast<const uint8_t*>(&SerializedType), sizeof(SerializedType));
	Ar.Write({TypeView, SourceView});
}

MemoryView
CbFieldView::GetView() const
{
	const uint32_t TypeSize	   = CbFieldTypeOps::HasFieldType(Type) ? sizeof(CbFieldType) : 0;
	const uint32_t NameSize	   = CbFieldTypeOps::HasFieldName(Type) ? NameLen + MeasureVarUInt(NameLen) : 0;
	const uint64_t PayloadSize = GetPayloadSize();
	return MemoryView(static_cast<const uint8_t*>(Payload) - TypeSize - NameSize, TypeSize + NameSize + PayloadSize);
}

MemoryView
CbFieldView::GetViewNoType() const
{
	const uint32_t NameSize	   = CbFieldTypeOps::HasFieldName(Type) ? NameLen + MeasureVarUInt(NameLen) : 0;
	const uint64_t PayloadSize = GetPayloadSize();
	return MemoryView(static_cast<const uint8_t*>(Payload) - NameSize, NameSize + PayloadSize);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

CbArrayView::CbArrayView() : CbFieldView(CompactBinaryPrivate::GEmptyArrayPayload)
{
}

uint64_t
CbArrayView::Num() const
{
	const uint8_t* PayloadBytes = static_cast<const uint8_t*>(GetPayload());
	PayloadBytes += MeasureVarUInt(PayloadBytes);
	uint32_t NumByteCount;
	return ReadVarUInt(PayloadBytes, NumByteCount);
}

CbFieldViewIterator
CbArrayView::CreateViewIterator() const
{
	const uint8_t* PayloadBytes = static_cast<const uint8_t*>(GetPayload());
	uint32_t	   PayloadSizeByteCount;
	const uint64_t PayloadSize = ReadVarUInt(PayloadBytes, PayloadSizeByteCount);
	PayloadBytes += PayloadSizeByteCount;
	const uint64_t NumByteCount = MeasureVarUInt(PayloadBytes);
	if (PayloadSize > NumByteCount)
	{
		const void* const PayloadEnd = PayloadBytes + PayloadSize;
		PayloadBytes += NumByteCount;
		const CbFieldType UniformType =
			CbFieldTypeOps::GetType(GetType()) == CbFieldType::UniformArray ? CbFieldType(*PayloadBytes++) : CbFieldType::HasFieldType;
		return CbFieldViewIterator::MakeRange(MemoryView(PayloadBytes, PayloadEnd), UniformType);
	}
	return CbFieldViewIterator();
}

void
CbArrayView::VisitFields(ICbVisitor&)
{
}

uint64_t
CbArrayView::GetSize() const
{
	return sizeof(CbFieldType) + GetPayloadSize();
}

IoHash
CbArrayView::GetHash() const
{
	IoHashStream Hash;
	GetHash(Hash);
	return Hash.GetHash();
}

void
CbArrayView::GetHash(IoHashStream& HashStream) const
{
	const CbFieldType SerializedType = CbFieldTypeOps::GetType(GetType());
	HashStream.Append(&SerializedType, sizeof(SerializedType));
	auto _ = GetPayloadView();
	HashStream.Append(_.GetData(), _.GetSize());
}

bool
CbArrayView::Equals(const CbArrayView& Other) const
{
	return CbFieldTypeOps::GetType(GetType()) == CbFieldTypeOps::GetType(Other.GetType()) &&
		   GetPayloadView().EqualBytes(Other.GetPayloadView());
}

void
CbArrayView::CopyTo(MutableMemoryView Buffer) const
{
	const MemoryView Source = GetPayloadView();
	ZEN_ASSERT_FORMAT(Buffer.GetSize() == sizeof(CbFieldType) + Source.GetSize(),
					  "Buffer is {} bytes but {} is required.",
					  Buffer.GetSize(),
					  sizeof(CbFieldType) + Source.GetSize());

	*static_cast<CbFieldType*>(Buffer.GetData()) = CbFieldTypeOps::GetType(GetType());
	Buffer.RightChopInline(sizeof(CbFieldType));
	memcpy(Buffer.GetData(), Source.GetData(), Source.GetSize());
}

void
CbArrayView::CopyTo(BinaryWriter& Ar) const
{
	const MemoryView SourceView		= GetPayloadView();
	CbFieldType		 SerializedType = CbFieldTypeOps::GetSerializedType(GetType());
	const MemoryView TypeView(reinterpret_cast<const uint8_t*>(&SerializedType), sizeof(SerializedType));
	Ar.Write({TypeView, SourceView});
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

CbObjectView::CbObjectView() : CbFieldView(CompactBinaryPrivate::GEmptyObjectPayload)
{
}

CbFieldViewIterator
CbObjectView::CreateViewIterator() const
{
	const uint8_t* PayloadBytes = static_cast<const uint8_t*>(GetPayload());
	uint32_t	   PayloadSizeByteCount;
	const uint64_t PayloadSize = ReadVarUInt(PayloadBytes, PayloadSizeByteCount);

	PayloadBytes += PayloadSizeByteCount;

	if (PayloadSize)
	{
		const void* const PayloadEnd = PayloadBytes + PayloadSize;
		const CbFieldType UniformType =
			CbFieldTypeOps::GetType(GetType()) == CbFieldType::UniformObject ? CbFieldType(*PayloadBytes++) : CbFieldType::HasFieldType;
		return CbFieldViewIterator::MakeRange(MemoryView(PayloadBytes, PayloadEnd), UniformType);
	}

	return CbFieldViewIterator();
}

void
CbObjectView::VisitFields(ICbVisitor&)
{
}

CbFieldView
CbObjectView::FindView(const std::string_view Name) const
{
	for (const CbFieldView& Field : *this)
	{
		if (Name == Field.GetName())
		{
			return Field;
		}
	}
	return CbFieldView();
}

CbFieldView
CbObjectView::FindViewIgnoreCase(const std::string_view Name) const
{
	for (const CbFieldView& Field : *this)
	{
		if (Name == Field.GetName())
		{
			return Field;
		}
	}
	return CbFieldView();
}

CbObjectView::operator bool() const
{
	return GetSize() > sizeof(CompactBinaryPrivate::GEmptyObjectPayload);
}

uint64_t
CbObjectView::GetSize() const
{
	return sizeof(CbFieldType) + GetPayloadSize();
}

IoHash
CbObjectView::GetHash() const
{
	IoHashStream Hash;
	GetHash(Hash);
	return Hash.GetHash();
}

void
CbObjectView::GetHash(IoHashStream& HashStream) const
{
	const CbFieldType SerializedType = CbFieldTypeOps::GetType(GetType());
	HashStream.Append(&SerializedType, sizeof(SerializedType));
	HashStream.Append(GetPayloadView());
}

bool
CbObjectView::Equals(const CbObjectView& Other) const
{
	return CbFieldTypeOps::GetType(GetType()) == CbFieldTypeOps::GetType(Other.GetType()) &&
		   GetPayloadView().EqualBytes(Other.GetPayloadView());
}

void
CbObjectView::CopyTo(MutableMemoryView Buffer) const
{
	const MemoryView Source = GetPayloadView();
	ZEN_ASSERT_FORMAT(Buffer.GetSize() == (sizeof(CbFieldType) + Source.GetSize()),
					  "Buffer is {} bytes but {} is required.",
					  Buffer.GetSize(),
					  sizeof(CbFieldType) + Source.GetSize());
	*static_cast<CbFieldType*>(Buffer.GetData()) = CbFieldTypeOps::GetType(GetType());
	Buffer.RightChopInline(sizeof(CbFieldType));
	memcpy(Buffer.GetData(), Source.GetData(), Source.GetSize());
}

void
CbObjectView::CopyTo(BinaryWriter& Ar) const
{
	const MemoryView SourceView		= GetPayloadView();
	CbFieldType		 SerializedType = CbFieldTypeOps::GetSerializedType(GetType());
	const MemoryView TypeView(reinterpret_cast<const uint8_t*>(&SerializedType), sizeof(SerializedType));
	Ar.Write({TypeView, SourceView});
}

//////////////////////////////////////////////////////////////////////////

template<typename FieldType>
uint64_t
TCbFieldIterator<FieldType>::GetRangeSize() const
{
	MemoryView View;
	if (TryGetSerializedRangeView(View))
	{
		return View.GetSize();
	}
	else
	{
		uint64_t Size = 0;
		for (CbFieldViewIterator It(*this); It; ++It)
		{
			Size += It.GetSize();
		}
		return Size;
	}
}

template<typename FieldType>
IoHash
TCbFieldIterator<FieldType>::GetRangeHash() const
{
	IoHashStream Hash;
	GetRangeHash(Hash);
	return IoHash(Hash.GetHash());
}

template<typename FieldType>
void
TCbFieldIterator<FieldType>::GetRangeHash(IoHashStream& Hash) const
{
	MemoryView View;
	if (TryGetSerializedRangeView(View))
	{
		Hash.Append(View.GetData(), View.GetSize());
	}
	else
	{
		for (CbFieldViewIterator It(*this); It; ++It)
		{
			It.GetHash(Hash);
		}
	}
}

template<typename FieldType>
void
TCbFieldIterator<FieldType>::CopyRangeTo(MutableMemoryView InBuffer) const
{
	MemoryView Source;
	if (TryGetSerializedRangeView(Source))
	{
		ZEN_ASSERT_FORMAT(InBuffer.GetSize() == Source.GetSize(),
						  "Buffer is {} bytes but {} is required.",
						  InBuffer.GetSize(),
						  Source.GetSize());
		memcpy(InBuffer.GetData(), Source.GetData(), Source.GetSize());
	}
	else
	{
		for (CbFieldViewIterator It(*this); It; ++It)
		{
			const uint64_t Size = It.GetSize();
			It.CopyTo(InBuffer.Left(Size));
			InBuffer.RightChopInline(Size);
		}
	}
}

template class TCbFieldIterator<CbFieldView>;
template class TCbFieldIterator<CbField>;

template<typename FieldType>
void
TCbFieldIterator<FieldType>::IterateRangeAttachments(std::function<void(CbFieldView)> Visitor) const
{
	if (CbFieldTypeOps::HasFieldType(FieldType::GetType()))
	{
		// Always iterate over non-uniform ranges because we do not know if they contain an attachment.
		for (CbFieldViewIterator It(*this); It; ++It)
		{
			if (CbFieldTypeOps::MayContainAttachments(It.GetType()))
			{
				It.IterateAttachments(Visitor);
			}
		}
	}
	else
	{
		// Only iterate over uniform ranges if the uniform type may contain an attachment.
		if (CbFieldTypeOps::MayContainAttachments(FieldType::GetType()))
		{
			for (CbFieldViewIterator It(*this); It; ++It)
			{
				It.IterateAttachments(Visitor);
			}
		}
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

CbFieldIterator
CbFieldIterator::CloneRange(const CbFieldViewIterator& It)
{
	MemoryView View;
	if (It.TryGetSerializedRangeView(View))
	{
		return MakeRange(SharedBuffer::Clone(View));
	}
	else
	{
		UniqueBuffer Buffer = UniqueBuffer::Alloc(It.GetRangeSize());
		It.CopyRangeTo(MutableMemoryView(Buffer.GetData(), Buffer.GetSize()));
		return MakeRange(SharedBuffer(std::move(Buffer)));
	}
}

SharedBuffer
CbFieldIterator::GetRangeBuffer() const
{
	const MemoryView	RangeView	= GetRangeView();
	const SharedBuffer& OuterBuffer = GetOuterBuffer();
	return OuterBuffer.GetView() == RangeView ? OuterBuffer : SharedBuffer::MakeView(RangeView, OuterBuffer);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

uint64_t
MeasureCompactBinary(MemoryView View, CbFieldType Type)
{
	uint64_t Size;
	return TryMeasureCompactBinary(View, Type, Size, Type) ? Size : 0;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool
TryMeasureCompactBinary(MemoryView View, CbFieldType& OutType, uint64_t& OutSize, CbFieldType Type)
{
	uint64_t Size = 0;

	if (CbFieldTypeOps::HasFieldType(Type))
	{
		if (View.GetSize() == 0)
		{
			OutType = CbFieldType::None;
			OutSize = 1;
			return false;
		}

		Type = *static_cast<const CbFieldType*>(View.GetData());
		View.RightChopInline(1);
		Size += 1;
	}

	bool	 bDynamicSize = false;
	uint64_t FixedSize	  = 0;
	switch (CbFieldTypeOps::GetType(Type))
	{
		case CbFieldType::Null:
			break;
		case CbFieldType::Object:
		case CbFieldType::UniformObject:
		case CbFieldType::Array:
		case CbFieldType::UniformArray:
		case CbFieldType::Binary:
		case CbFieldType::String:
		case CbFieldType::IntegerPositive:
		case CbFieldType::IntegerNegative:
			bDynamicSize = true;
			break;
		case CbFieldType::Float32:
			FixedSize = 4;
			break;
		case CbFieldType::Float64:
			FixedSize = 8;
			break;
		case CbFieldType::BoolFalse:
		case CbFieldType::BoolTrue:
			break;
		case CbFieldType::ObjectAttachment:
		case CbFieldType::BinaryAttachment:
		case CbFieldType::Hash:
			FixedSize = 20;
			break;
		case CbFieldType::Uuid:
			FixedSize = 16;
			break;
		case CbFieldType::ObjectId:
			FixedSize = 12;
			break;
		case CbFieldType::DateTime:
		case CbFieldType::TimeSpan:
			FixedSize = 8;
			break;
		case CbFieldType::None:
		default:
			OutType = CbFieldType::None;
			OutSize = 0;
			return false;
	}

	OutType = Type;

	if (CbFieldTypeOps::HasFieldName(Type))
	{
		if (View.GetSize() == 0)
		{
			OutSize = Size + 1;
			return false;
		}

		uint32_t NameLenByteCount = MeasureVarUInt(View.GetData());
		if (View.GetSize() < NameLenByteCount)
		{
			OutSize = Size + NameLenByteCount;
			return false;
		}

		const uint64_t NameLen	= ReadMeasuredVarUInt(View.GetData(), NameLenByteCount);
		const uint64_t NameSize = NameLen + NameLenByteCount;

		if (bDynamicSize && View.GetSize() < NameSize)
		{
			OutSize = Size + NameSize;
			return false;
		}

		View.RightChopInline(NameSize);
		Size += NameSize;
	}

	switch (CbFieldTypeOps::GetType(Type))
	{
		case CbFieldType::Object:
		case CbFieldType::UniformObject:
		case CbFieldType::Array:
		case CbFieldType::UniformArray:
		case CbFieldType::Binary:
		case CbFieldType::String:
			if (View.GetSize() == 0)
			{
				OutSize = Size + 1;
				return false;
			}
			else
			{
				uint32_t PayloadSizeByteCount = MeasureVarUInt(View.GetData());
				if (View.GetSize() < PayloadSizeByteCount)
				{
					OutSize = Size + PayloadSizeByteCount;
					return false;
				}
				const uint64_t PayloadSize = ReadMeasuredVarUInt(View.GetData(), PayloadSizeByteCount);
				OutSize					   = Size + PayloadSize + PayloadSizeByteCount;
			}
			return true;

		case CbFieldType::IntegerPositive:
		case CbFieldType::IntegerNegative:
			if (View.GetSize() == 0)
			{
				OutSize = Size + 1;
				return false;
			}
			OutSize = Size + MeasureVarUInt(View.GetData());
			return true;

		default:
			OutSize = Size + FixedSize;
			return true;
	}
}

//////////////////////////////////////////////////////////////////////////

CbField
LoadCompactBinary(BinaryReader& Ar, BufferAllocator Allocator)
{
	std::vector<uint8_t> HeaderBytes;
	CbFieldType			 FieldType;
	uint64_t			 FieldSize = 1;

	for (const int64_t StartPos = Ar.CurrentOffset(); FieldSize > 0;)
	{
		// Read in small increments until the total field size is known, to avoid reading too far.
		const int32_t ReadSize = int32_t(FieldSize - HeaderBytes.size());
		if (Ar.CurrentOffset() + ReadSize > Ar.GetSize())
		{
			break;
		}

		const size_t ReadOffset = HeaderBytes.size();
		HeaderBytes.resize(ReadOffset + ReadSize);

		Ar.Read(HeaderBytes.data() + ReadOffset, ReadSize);
		if (TryMeasureCompactBinary(MakeMemoryView(HeaderBytes), FieldType, FieldSize))
		{
			if (FieldSize <= uint64_t(Ar.Size() - StartPos))
			{
				UniqueBuffer Buffer = Allocator(FieldSize);
				ZEN_ASSERT(Buffer.GetSize() == FieldSize);
				MutableMemoryView View = Buffer.GetMutableView();
				memcpy(View.GetData(), HeaderBytes.data(), HeaderBytes.size());
				View.RightChopInline(HeaderBytes.size());
				if (!View.IsEmpty())
				{
					// Read the remainder of the field.
					Ar.Read(View.GetData(), View.GetSize());
				}
				if (ValidateCompactBinary(Buffer, CbValidateMode::Default) == CbValidateError::None)
				{
					return CbField(SharedBuffer(std::move(Buffer)));
				}
			}
			break;
		}
	}
	return CbField();
}

CbObject
LoadCompactBinaryObject(IoBuffer&& Payload)
{
	if (Payload.GetSize() == 0)
	{
		return CbObject();
	}
	return CbObject{SharedBuffer(std::move(Payload))};
}

CbObject
LoadCompactBinaryObject(const IoBuffer& Payload)
{
	if (Payload.GetSize() == 0)
	{
		return CbObject();
	}
	return CbObject{SharedBuffer(Payload)};
}

CbObject
LoadCompactBinaryObject(CompressedBuffer&& Payload)
{
	CompositeBuffer Decompressed = std::move(Payload).DecompressToComposite();
	if (Decompressed.GetSize() == 0)
	{
		return CbObject();
	}
	return CbObject{std::move(Decompressed).Flatten()};
}

CbObject
LoadCompactBinaryObject(const CompressedBuffer& Payload)
{
	CompositeBuffer Decompressed = Payload.DecompressToComposite();
	if (Decompressed.GetSize() == 0)
	{
		return CbObject();
	}
	return CbObject{std::move(Decompressed).Flatten()};
}

//////////////////////////////////////////////////////////////////////////

void
SaveCompactBinary(BinaryWriter& Ar, const CbFieldView& Field)
{
	Field.CopyTo(Ar);
}

void
SaveCompactBinary(BinaryWriter& Ar, const CbArrayView& Array)
{
	Array.CopyTo(Ar);
}

void
SaveCompactBinary(BinaryWriter& Ar, const CbObjectView& Object)
{
	Object.CopyTo(Ar);
}

//////////////////////////////////////////////////////////////////////////

std::vector<CbFieldView>
ReadCompactBinaryStream(MemoryView Data)
{
	std::vector<CbFieldView> Result;
	const uint8_t*			 Buffer = reinterpret_cast<const uint8_t*>(Data.GetData());
	uint64_t				 Offset = 0;
	const uint64_t			 Size	= Data.GetSize();
	while (Offset < Size)
	{
		if (ValidateCompactBinary(MemoryView(Buffer + Offset, Size - Offset), CbValidateMode::Default) != CbValidateError::None)
		{
			break;
		}
		CbFieldView Field(Buffer + Offset);
		Offset += Field.GetSize();
		Result.emplace_back(std::move(Field));
	}
	return Result;
}

//////////////////////////////////////////////////////////////////////////

#if ZEN_WITH_TESTS
void
uson_forcelink()
{
}

TEST_CASE("guid")
{
	using namespace std::literals;

	const Guid A = Guid::FromString("03000c43-d267-36fd-9164-a7555824822b"sv);

	StringBuilder<40> GuidStr;
	A.ToString(GuidStr);

	CHECK(std::string_view(GuidStr) == "03000c43-d267-36fd-9164-a7555824822b"sv);

	const Guid B = Guid::FromString(GuidStr);

	CHECK(A == B);
}

TEST_CASE("uson")
{
	using namespace std::literals;

	SUBCASE("CbField")
	{
		constexpr CbFieldView DefaultField;
		static_assert(!DefaultField.HasName(), "Error in HasName()");
		static_assert(!DefaultField.HasValue(), "Error in HasValue()");
		static_assert(!DefaultField.HasError(), "Error in HasError()");
		static_assert(DefaultField.GetError() == CbFieldError::None, "Error in GetError()");

		CHECK(DefaultField.GetSize() == 1);
		CHECK(DefaultField.GetName().size() == 0);
		CHECK(DefaultField.HasName() == false);
		CHECK(DefaultField.HasValue() == false);
		CHECK(DefaultField.HasError() == false);
		CHECK(DefaultField.GetError() == CbFieldError::None);

		const uint8_t Type = (uint8_t)CbFieldType::None;
		CHECK(DefaultField.GetHash() == IoHash::HashBuffer(&Type, sizeof Type));

		CHECK(DefaultField.GetView() == MemoryView{});
		MemoryView SerializedView;
		CHECK(DefaultField.TryGetSerializedView(SerializedView) == false);
	}

	SUBCASE("CbField(None)")
	{
		CbFieldView NoneField(nullptr, CbFieldType::None);
		CHECK(NoneField.GetSize() == 1);
		CHECK(NoneField.GetName().size() == 0);
		CHECK(NoneField.HasName() == false);
		CHECK(NoneField.HasValue() == false);
		CHECK(NoneField.HasError() == false);
		CHECK(NoneField.GetError() == CbFieldError::None);
		CHECK(NoneField.GetHash() == CbFieldView().GetHash());
		CHECK(NoneField.GetView() == MemoryView());
		MemoryView SerializedView;
		CHECK(NoneField.TryGetSerializedView(SerializedView) == false);
	}

	SUBCASE("CbField(None|Type|Name)")
	{
		constexpr CbFieldType FieldType	  = CbFieldType::None | CbFieldType::HasFieldName;
		const char			  NoneBytes[] = {char(FieldType), 4, 'N', 'a', 'm', 'e'};
		CbFieldView			  NoneField(NoneBytes);

		CHECK(NoneField.GetSize() == sizeof(NoneBytes));
		CHECK(NoneField.GetName().compare("Name"sv) == 0);
		CHECK(NoneField.HasName() == true);
		CHECK(NoneField.HasValue() == false);
		CHECK(NoneField.GetHash() == IoHash::HashBuffer(NoneBytes, sizeof NoneBytes));
		CHECK(NoneField.GetView() == MemoryView(NoneBytes, sizeof NoneBytes));
		MemoryView SerializedView;
		CHECK(NoneField.TryGetSerializedView(SerializedView) == true);
		CHECK(SerializedView == MemoryView(NoneBytes, sizeof NoneBytes));

		uint8_t CopyBytes[sizeof(NoneBytes)];
		NoneField.CopyTo(MutableMemoryView(CopyBytes, sizeof CopyBytes));
		CHECK(MemoryView(NoneBytes, sizeof NoneBytes).EqualBytes(MemoryView(CopyBytes, sizeof CopyBytes)));
	}

	SUBCASE("CbField(None|Type)")
	{
		constexpr CbFieldType FieldType	  = CbFieldType::None;
		const char			  NoneBytes[] = {char(FieldType)};
		CbFieldView			  NoneField(NoneBytes);

		CHECK(NoneField.GetSize() == sizeof NoneBytes);
		CHECK(NoneField.GetName().size() == 0);
		CHECK(NoneField.HasName() == false);
		CHECK(NoneField.HasValue() == false);
		CHECK(NoneField.GetHash() == CbFieldView().GetHash());
		CHECK(NoneField.GetView() == MemoryView(NoneBytes, sizeof NoneBytes));
		MemoryView SerializedView;
		CHECK(NoneField.TryGetSerializedView(SerializedView) == true);
		CHECK(SerializedView == MemoryView(NoneBytes, sizeof NoneBytes));
	}

	SUBCASE("CbField(None|Name)")
	{
		constexpr CbFieldType FieldType	  = CbFieldType::None | CbFieldType::HasFieldName;
		const char			  NoneBytes[] = {char(FieldType), 4, 'N', 'a', 'm', 'e'};
		CbFieldView			  NoneField(NoneBytes + 1, FieldType);
		CHECK(NoneField.GetSize() == uint64_t(sizeof NoneBytes));
		CHECK(NoneField.GetName().compare("Name") == 0);
		CHECK(NoneField.HasName() == true);
		CHECK(NoneField.HasValue() == false);
		CHECK(NoneField.GetHash() == IoHash::HashBuffer(NoneBytes, sizeof NoneBytes));
		CHECK(NoneField.GetView() == MemoryView(NoneBytes + 1, sizeof NoneBytes - 1));
		MemoryView SerializedView;
		CHECK(NoneField.TryGetSerializedView(SerializedView) == false);

		uint8_t CopyBytes[sizeof(NoneBytes)];
		NoneField.CopyTo(MutableMemoryView(CopyBytes, sizeof CopyBytes));
		CHECK(MemoryView(NoneBytes, sizeof NoneBytes).EqualBytes(MemoryView(CopyBytes, sizeof CopyBytes)));
	}

	SUBCASE("CbField(None|EmptyName)")
	{
		constexpr CbFieldType FieldType	  = CbFieldType::None | CbFieldType::HasFieldName;
		const uint8_t		  NoneBytes[] = {uint8_t(FieldType), 0};
		CbFieldView			  NoneField(NoneBytes + 1, FieldType);
		CHECK(NoneField.GetSize() == sizeof NoneBytes);
		CHECK(NoneField.GetName().empty() == true);
		CHECK(NoneField.HasName() == true);
		CHECK(NoneField.HasValue() == false);
		CHECK(NoneField.GetHash() == IoHash::HashBuffer(NoneBytes, sizeof NoneBytes));
		CHECK(NoneField.GetView() == MemoryView(NoneBytes + 1, sizeof NoneBytes - 1));
		MemoryView SerializedView;
		CHECK(NoneField.TryGetSerializedView(SerializedView) == false);
	}

	static_assert(!std::is_constructible<CbFieldView, const CbObjectView&>::value, "Invalid constructor for CbField");
	static_assert(!std::is_assignable<CbFieldView, const CbObjectView&>::value, "Invalid assignment for CbField");
	static_assert(!std::is_convertible<CbFieldView, CbObjectView>::value, "Invalid conversion to CbObject");
	static_assert(!std::is_assignable<CbObjectView, const CbFieldView&>::value, "Invalid assignment for CbObject");

	static_assert(std::is_constructible<CbField>::value, "Missing constructor for CbField");
	static_assert(std::is_constructible<CbField, const CbField&>::value, "Missing constructor for CbField");
	static_assert(std::is_constructible<CbField, CbField&&>::value, "Missing constructor for CbField");
}

TEST_CASE("uson.null")
{
	using namespace std::literals;

	SUBCASE("CbField(Null)")
	{
		CbFieldView NullField(nullptr, CbFieldType::Null);
		CHECK(NullField.GetSize() == 1);
		CHECK(NullField.IsNull() == true);
		CHECK(NullField.HasValue() == true);
		CHECK(NullField.HasError() == false);
		CHECK(NullField.GetError() == CbFieldError::None);
		const uint8_t Null[]{uint8_t(CbFieldType::Null)};
		CHECK(NullField.GetHash() == IoHash::HashBuffer(Null, sizeof Null));
	}

	SUBCASE("CbField(None)")
	{
		CbFieldView Field;
		CHECK(Field.IsNull() == false);
	}
}

TEST_CASE("uson.datetime")
{
	using namespace std::literals;

	{
		DateTime D1600(1601, 1, 1);
		CHECK_EQ(D1600.GetYear(), 1601);
		CHECK_EQ(D1600.GetMonth(), 1);
		CHECK_EQ(D1600.GetDay(), 1);
		CHECK_EQ(D1600.GetHour(), 0);
		CHECK_EQ(D1600.GetMinute(), 0);
		CHECK_EQ(D1600.GetSecond(), 0);

		CHECK_EQ(D1600.ToIso8601(), "1601-01-01T00:00:00.000Z"sv);
	}

	{
		DateTime D72(1972, 2, 23, 17, 30, 10);
		CHECK_EQ(D72.GetYear(), 1972);
		CHECK_EQ(D72.GetMonth(), 2);
		CHECK_EQ(D72.GetDay(), 23);
		CHECK_EQ(D72.GetHour(), 17);
		CHECK_EQ(D72.GetMinute(), 30);
		CHECK_EQ(D72.GetSecond(), 10);
	}
}

//////////////////////////////////////////////////////////////////////////

TEST_SUITE_BEGIN("core.datetime");

TEST_CASE("core.datetime.compare")
{
	DateTime T1(2000, 12, 13);
	DateTime T2(2000, 12, 14);
	CHECK(T1 < T2);
	CHECK(T2 > T1);
	CHECK(T1 == T1);
	CHECK(T1 != T2);
	CHECK(T1 >= T1);
	CHECK(T2 >= T1);
	CHECK(T1 <= T1);
	CHECK(T1 <= T2);
}

TEST_CASE("core.datetime.add")
{
	DateTime T1(2000, 12, 13);
	DateTime T2(2000, 12, 14);
	TimeSpan dT	 = T2 - T1;
	TimeSpan dT1 = T1 - T1;

	CHECK(T1 + dT == T2);
	CHECK(dT + T1 == T2);
	CHECK(dT + T1 - T2 == dT1);
}

TEST_SUITE_END();

TEST_SUITE_BEGIN("core.timespan");

TEST_CASE("core.timespan.compare")
{
	TimeSpan T1(1000);
	TimeSpan T2(1001);
	CHECK(T1 < T2);
	CHECK(T2 > T1);
	CHECK(T1 == T1);
	CHECK(T1 != T2);
	CHECK(T1 >= T1);
	CHECK(T2 >= T1);
	CHECK(T1 <= T1);
	CHECK(T1 <= T2);
}

TEST_SUITE_END();

//////////////////////////////////////////////////////////////////////////

#endif

}  // namespace zen
